package ie.lawlor.amvc.config;

import ie.lawlor.amvc.Triad;
import ie.lawlor.amvc.ViewContainer;
import ie.lawlor.amvc.controller.ToModelEvent;
import ie.lawlor.amvc.event.EventName;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ChildBeanDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

class TriadTypeDefinitionParser implements BeanDefinitionParser {

	private static final String MODEL = "model";
	private static final String VIEW = "view";
	private static final String CONTROLLER = "controller";
	private static final String INPUT_EVENTS = "inputEvents";
	private static final String OUTPUT_EVENTS = "outputEvents";
	private static final String TRIAD_EVENTS = "triadEvents";
	
	ManagedMap inputEventBeans = new ManagedMap();
	ManagedMap outputEventBeans = new ManagedMap();
	ManagedList triadEventBeans = new ManagedList();
	BeanDefinition model = null;
	BeanDefinition view = null;
	BeanDefinition controller = null;
	String triadId;

	public BeanDefinition parse(Element element, ParserContext parserContext) {
		BeanDefinitionRegistry registry = parserContext.getRegistry();
		triadId = element.getAttribute(AbstractTriadDefinitionParser.ID);
		BeanDefinition triadTypeDefinition = createTriadTypeBeanDefinition(element);
		registry.registerBeanDefinition(triadId, triadTypeDefinition);
		// Then create its children: TODO This is the wrong method to call - it gives ALL descendants, not just direct ones. 
		NodeList childNodes = element.getChildNodes();
		for (int i = 0; i < childNodes.getLength(); ++i) {
			Node childNode = childNodes.item(i);
			createChildBeanDefinition(childNode, registry);
		}
		// Finish off the triad's properties with those created as part of
		MutablePropertyValues propertyValues = triadTypeDefinition
				.getPropertyValues();
		propertyValues.addPropertyValue(MODEL, createReference(model));
		propertyValues.addPropertyValue(VIEW, new RuntimeBeanReference((String)view.getAttribute("id")));
		propertyValues.addPropertyValue(CONTROLLER, new RuntimeBeanReference((String)controller.getAttribute("id")));
		propertyValues.addPropertyValue(INPUT_EVENTS, inputEventBeans);
		propertyValues.addPropertyValue(OUTPUT_EVENTS, outputEventBeans);
		propertyValues.addPropertyValue(TRIAD_EVENTS, triadEventBeans);
		return triadTypeDefinition;
	}
	
	private RuntimeBeanReference createReference(BeanDefinition beanDefinition){
		if (beanDefinition == null){
			return null;
		} else {
			return new RuntimeBeanReference((String)beanDefinition.getAttribute("id"));
		}
	}

	private BeanDefinition createTriadTypeBeanDefinition(Element element) {
		BeanDefinition triadTypeDefinition = null;
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		if (element.getAttribute("class") != null && element.getAttribute("class").length()>0){
			triadTypeDefinition = new RootBeanDefinition(element.getAttribute("class"), new ConstructorArgumentValues(), propertyValues);
		} else if (element.getAttribute("type") != null && element.getAttribute("type").length()>0){
				triadTypeDefinition = new ChildBeanDefinition(element.getAttribute("type"), propertyValues);
		} else {
			triadTypeDefinition = new RootBeanDefinition(Triad.class, propertyValues);
		}
		triadTypeDefinition.setAttribute(AbstractTriadDefinitionParser.ID, element
				.getAttribute(AbstractTriadDefinitionParser.ID));
		propertyValues.addPropertyValue(AbstractTriadDefinitionParser.NAME, element
				.getAttribute(AbstractTriadDefinitionParser.NAME));
		return triadTypeDefinition;
	}

	private void createChildBeanDefinition(Node node,
			BeanDefinitionRegistry registry) {
		if ("componentEvent".equals(node.getLocalName())) {
			BeanDefinition eventNameBean = createComponentEventName(node);
			String eventId = (String) eventNameBean.getAttribute("id");
			String type = node.getAttributes().getNamedItem("type")
					.getNodeValue();
			if ("in".equalsIgnoreCase(type) || "inout".equalsIgnoreCase(type)) {
				inputEventBeans.put(eventId, new RuntimeBeanReference(eventId));
			} else if ("out".equalsIgnoreCase(type) || "inout".equalsIgnoreCase(type)){
				outputEventBeans.put(eventId, new RuntimeBeanReference(eventId));
			}
			registry.registerBeanDefinition(eventId, eventNameBean);
		} else if ("model".equals(node.getLocalName())) {
			model = createModel(node);
			registerById(registry, model);
		} else if ("view".equals(node.getLocalName())) {
			view = createView(node, registry);
			registerById(registry, view);
		} else if ("controller".equals(node.getLocalName())) {
			controller = createController(node);
			if (controller == null){
				throw new IllegalStateException("Cannot allow a null controller.");
			}
			registerById(registry, controller);
		}
		if (view != null){
			MutablePropertyValues propertyValues = view.getPropertyValues();
			propertyValues.addPropertyValue("controller", new RuntimeBeanReference((String)controller.getAttribute("id")));
//			view.setPropertyValues(propertyValues);
		} 
		if (model != null){
		}
	}

	private void registerById(BeanDefinitionRegistry registry, BeanDefinition beanDefinition) {
		String id = (String)beanDefinition.getAttribute("id");
		if (id != null){
			registry.registerBeanDefinition(id, beanDefinition);
		}
	}


	private BeanDefinition createComponentEventName(Node node) {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(
				EventName.class);
		MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
		Node attr = null;
		String eventBeanNameId = null;
		if ((attr = node.getAttributes().getNamedItem("id")) != null) {
			eventBeanNameId = attr.getNodeValue();
			beanDefinition.setAttribute("id", eventBeanNameId);
		}
		if ((attr = node.getAttributes().getNamedItem("name")) != null) {
			propertyValues.addPropertyValue("name", attr.getNodeValue());
		}
		if ((attr = node.getAttributes().getNamedItem("nameSpace")) != null) {
			propertyValues.addPropertyValue("nameSpace", attr.getNodeValue());
		}
		beanDefinition.setPropertyValues(propertyValues);
		NodeList childNodes = node.getChildNodes();
		for (int i = 0; i < childNodes.getLength(); ++i) {
			Node childNode = childNodes.item(i);
			createEventRoutingDefinition(childNode, eventBeanNameId);
		}
		return beanDefinition;
	}
	

	private void createEventRoutingDefinition(Node childNode, String eventBeanNameId) {
		// TODO Complete for other routing types
		if ("toModel".equals(childNode.getLocalName())){
			triadEventBeans.add(createToModelEvent(childNode, eventBeanNameId));
		}
	}
	

	private Object createToModelEvent(Node node, String eventBeanName) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(
                ToModelEvent.class);
        ConstructorArgumentValues constructorArgs = beanDefinition.getConstructorArgumentValues();
        constructorArgs.addIndexedArgumentValue(0, new RuntimeBeanReference(eventBeanName));
        beanDefinition.setConstructorArgumentValues(constructorArgs);
        return beanDefinition;
	}

	private BeanDefinition createView(Node node, BeanDefinitionRegistry registry) {
		BeanDefinition beanDefinition = null;
		String beanClassName = null;
		String beanType = null;
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues();
		Node attr = null;
		if ((attr = node.getAttributes().getNamedItem("class")) != null) {
			beanClassName =  attr.getNodeValue();
		}
		if ((attr = node.getAttributes().getNamedItem("type")) != null) {
			beanType =  attr.getNodeValue();
		}
		if ((attr = node.getAttributes().getNamedItem("name")) != null) {
			constructorArgs.addIndexedArgumentValue(0, attr.getNodeValue());			
		}
		if (beanClassName != null){
			beanDefinition = new RootBeanDefinition(beanClassName, constructorArgs, propertyValues);
			((RootBeanDefinition)beanDefinition).setSingleton(false);
		} else {
			beanDefinition = new ChildBeanDefinition(beanType, constructorArgs, propertyValues);
			((ChildBeanDefinition)beanDefinition).setSingleton(false);
		}
		beanDefinition.setAttribute("id", triadId+"View");
		NodeList childNodes = node.getChildNodes();
		for (int i = 0; i < childNodes.getLength(); ++i) {
			Node childNode = childNodes.item(i);
			createSubViewDefinition(childNode, registry);
		}
		return beanDefinition;
	}
	

	private void createSubViewDefinition(Node childNode, BeanDefinitionRegistry registry) {
		if (childNode.getAttributes() != null){
			String name = childNode.getAttributes().getNamedItem("name").getNodeValue();
			String id = childNode.getAttributes().getNamedItem("id").getNodeValue();
	        RootBeanDefinition beanDefinition = new RootBeanDefinition(ViewContainer.class);
	        beanDefinition.setAttribute("id", id);
			MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
			propertyValues.addPropertyValue("name", name);
	        registry.registerBeanDefinition(id, beanDefinition);
		}
	}

	private BeanDefinition createController(Node node) {
		ChildBeanDefinition beanDefinition = null;
		String parentName = null;
		Node attr = null;
		if ((attr = node.getAttributes().getNamedItem("type")) != null) {
			parentName =  attr.getNodeValue();
		}
		if (parentName != null){
			beanDefinition = new ChildBeanDefinition(parentName);
			MutablePropertyValues propertyValues = beanDefinition
					.getPropertyValues();
			beanDefinition.setAttribute("id", triadId+"Controller");
			beanDefinition.setPropertyValues(propertyValues);
			beanDefinition.setSingleton(false);
		}
		return beanDefinition;
	}	
	
	private RootBeanDefinition createModel(Node node) {
		RootBeanDefinition beanDefinition = null;
		String beanClassName = null;
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues();
		Node attr = null;
		if ((attr = node.getAttributes().getNamedItem("class")) != null) {
			beanClassName =  attr.getNodeValue();
		}
		if ((attr = node.getAttributes().getNamedItem("name")) != null) {
			constructorArgs.addIndexedArgumentValue(0, attr.getNodeValue());			
		}
		beanDefinition = new RootBeanDefinition(beanClassName, constructorArgs, propertyValues);
		beanDefinition.setAttribute("id", triadId+"Model");
		beanDefinition.setSingleton(false);
		return beanDefinition;
	}		
}
